/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.wsdl.support.policy;

import com.eviware.soapui.impl.support.definition.InterfaceDefinitionPart;
import com.eviware.soapui.impl.wsdl.support.wsa.WsaUtils;
import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.xml.XmlUtils;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.w3.x2007.x05.addressing.metadata.AddressingDocument.Addressing;
import org.w3c.dom.Element;
import org.xmlsoap.schemas.ws.x2004.x09.policy.OptionalType;
import org.xmlsoap.schemas.ws.x2004.x09.policy.Policy;
import org.xmlsoap.schemas.ws.x2004.x09.policy.PolicyDocument;

import javax.wsdl.Definition;
import javax.wsdl.extensions.ElementExtensible;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class PolicyUtils {
    public final static String WS_XMLSOAP_POLICY_NAMESPACE = "http://schemas.xmlsoap.org/ws/2004/09/policy";
    public final static String WS_W3_POLICY_NAMESPACE = "http://www.w3.org/ns/ws-policy";
    public final static String WS_SECURITY_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";

    public static List<Policy> getPolicies(WsdlContext wsdlContext) {

        List<Policy> policies = new ArrayList<Policy>();
        try {
            List<InterfaceDefinitionPart> parts = wsdlContext.getDefinitionCache().getDefinitionParts();
            for (int i = 0; i < parts.size(); i++) {
                InterfaceDefinitionPart part = parts.get(i);
                String content = part.getContent();
                // XmlObject xml = XmlObject.Factory.parse( content );
                XmlObject xml = XmlUtils.createXmlObject(content);
                // include paths for both namespaces
                XmlObject[] paths = xml.selectPath("declare namespace wsp='" + WS_W3_POLICY_NAMESPACE + "';"
                        + "//wsp:Policy");
                List<XmlObject> listOfXmlObjcts = Arrays.asList(paths);

                XmlObject[] paths1 = xml.selectPath("declare namespace wsp='" + WS_XMLSOAP_POLICY_NAMESPACE + "';"
                        + "//wsp:Policy");
                listOfXmlObjcts.addAll(Arrays.asList(paths1));
                paths = (XmlObject[]) listOfXmlObjcts.toArray();

                for (XmlObject obj : paths) {
                    String xx = obj.xmlText(new XmlOptions().setSaveOuter());
                    PolicyDocument policyDocument = PolicyDocument.Factory.parse(xx);
                    org.xmlsoap.schemas.ws.x2004.x09.policy.Policy polc = (org.xmlsoap.schemas.ws.x2004.x09.policy.Policy) policyDocument
                            .getPolicy();
                    policies.add(polc);
                    // List<Addressing> addressingList = polc.getAddressingList();
                    // Addressing a = null;
                    // if (addressingList.size() > 0 )
                    // {
                    // a = addressingList.get(0);
                    // }
                    // AnonymousResponses ar = null;
                    // List<AnonymousResponses> anList =
                    // polc.getAnonymousResponsesList();
                    // if (anList.size() > 0)
                    // {
                    // ar = anList.get(0);
                    // }

                }

            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return null;
    }

    public static boolean isAddressing(Policy policy) {

        if (policy.getAddressingList().size() > 0) {
            return true;
        }

        return false;
    }

    public static List<Policy> getAddressingPolicies(WsdlContext wsdlContext) {
        List<Policy> addressingPolicies = new ArrayList<Policy>();
        List<Policy> policies = getPolicies(wsdlContext);
        for (Policy policy : policies) {
            if (isAddressing(policy)) {
                addressingPolicies.add(policy);
            }
        }
        return addressingPolicies;
    }

	/*
     * Functions currently not used, initially intended for policy to be
	 * normalized first
	 */
    // public static Policy normalize(Policy policy) {
    // 1.Start with the Element Information Item E (as defined in the XML
    // Information Set [XML Information Set]) of the policy expression.
    // The [namespace name] of E is always "http://www.w3.org/ns/ws-policy". In
    // the base case, the [local name] property of E is "Policy";
    // in the recursive case, the [local name] property of E is "Policy",
    // "ExactlyOne", or "All".
    //
    // 2.Expand Element Information Items (as defined in the XML Information Set
    // [XML Information Set]) in the [children] property of E that
    // are policy references per Section 4.3.5 Policy Inclusion.
    //
    // 3.Convert each Element Information Item C in the [children] property of E
    // into normal form.
    // List<OperatorContentType> eoList = policy.getExactlyOneList();
    // ExactlyOneDocument.Factory.newInstance();
    //
    // 3.1 If the [namespace name] property of C is
    // "http://www.w3.org/ns/ws-policy" and the [local name] property of C is
    // "Policy",
    // "ExactlyOne", or "All", C is an expression of a policy operator; normalize
    // C by recursively applying this procedure.
    //
    // 3.2 Otherwise the Element Information Item C is an assertion; normalize C
    // per Sections 4.3.1 Optional Policy Assertions and 4.3.2
    // Policy Assertion Nesting.
    //
    // 4.Apply the policy operator indicated by E to the normalized Element
    // Information Items in its [children] property and co1.nstruct a
    // normal form per Section 4.3.3 Policy Operators and 4.1 Normal Form Policy
    // Expression.
    //

    // return policy;
    // }
	/*
	 * Functions currently not used, initially intended for policy to be
	 * normalized first
	 */
    // public static Element normalize(Element policy)
    // {
    // // if (!StringUtils.isNullOrEmpty(nameSpace) &&
    // !StringUtils.isNullOrEmpty(localName))
    // // {
    // NodeList nl = policy.getChildNodes();
    // List<Element> listElms = new ArrayList<Element>();
    // for( int c = 0; c < nl.getLength(); c++ )
    // {
    // Node item = nl.item( c );
    // if( item.getParentNode() == policy && item.getNodeType() ==
    // Node.ELEMENT_NODE )
    // listElms.add( (Element) item );
    // }
    //
    // for (int i = 0; i < listElms.size(); i++)
    // {
    // Element elm = listElms.get(i);
    // Element newElm = null;
    // String nameSpace = elm.getNamespaceURI();
    // String localName = elm.getLocalName();
    // if (nameSpace.equals(WS_W3_POLICY_NAMESPACE)
    // && (localName.equals("Policy") || localName.equals("All") ||
    // localName.equals("ExactlyOne")))
    // {
    // newElm = normalize(elm);
    //
    // } else {
    //
    // Element allElm =
    // elm.getOwnerDocument().createElementNS(WS_W3_POLICY_NAMESPACE, "All");
    // allElm.appendChild(elm);
    //
    // Element exactlyOneElm =
    // elm.getOwnerDocument().createElementNS(WS_W3_POLICY_NAMESPACE,
    // "ExactlyOne");
    // exactlyOneElm.appendChild(allElm);
    //
    // String optional = elm.getAttributeNS(WS_W3_POLICY_NAMESPACE, "Optional");
    // if (!StringUtils.isNullOrEmpty(optional) && optional.equals("true"))
    // {
    // Element allElmEmpty =
    // elm.getOwnerDocument().createElementNS(WS_W3_POLICY_NAMESPACE, "All");
    // exactlyOneElm.appendChild(allElmEmpty);
    // }
    //
    // newElm = exactlyOneElm;
    // }
    // elm.getParentNode().replaceChild(elm, newElm);
    // }
    // // }
    //
    // return policy;
    // }
    public static Policy getAttachedPolicy(ElementExtensible item, Definition def) {

        Policy rtnPolicy = null;
        String usedPolicyNamespace = PolicyUtils.WS_W3_POLICY_NAMESPACE;
        Element[] policyReferences = WsdlUtils.getExentsibilityElements(item, new QName(
                PolicyUtils.WS_W3_POLICY_NAMESPACE, "PolicyReference"));
        if (policyReferences.length <= 0) {
            policyReferences = WsdlUtils.getExentsibilityElements(item, new QName(
                    PolicyUtils.WS_XMLSOAP_POLICY_NAMESPACE, "PolicyReference"));
            usedPolicyNamespace = PolicyUtils.WS_XMLSOAP_POLICY_NAMESPACE;
        }
        if (policyReferences.length > 0) {
            String policyId = policyReferences[0].getAttribute("URI");
            if (!StringUtils.isNullOrEmpty(policyId)) {
                Element[] policies = WsdlUtils.getExentsibilityElements(def, new QName(usedPolicyNamespace, "Policy"));
                Element policy = null;
                for (int i = 0; i < policies.length; i++) {
                    policy = policies[i];
                    String policyIdx = policy.getAttributeNS(WS_SECURITY_NAMESPACE, "Id");
                    if (policyId.equals("#" + policyIdx)) {
                        rtnPolicy = getPolicy(policy, usedPolicyNamespace);
                        continue;
                    }

                }
            }
        } else {
            // get policies of item itself
            Element[] itemPolicies = WsdlUtils.getExentsibilityElements(item, new QName(usedPolicyNamespace, "Policy"));
            if (itemPolicies.length > 0) {
                for (int i = 0; i < itemPolicies.length; i++) {
                    Element policy = itemPolicies[i];
                    rtnPolicy = getPolicy(policy, usedPolicyNamespace);

                }
            }
        }
        return rtnPolicy;
    }

    public static Policy getPolicy(Element policy, String usedPolicyNamespace) {
        // policy = PolicyUtils.normalize(policy);

        Policy newPolicy = null;
        // check for ExactlyOne and All
        // TODO ExactlyOne and All are idempotent all empty ones should be skipped
        // and found the real ones
        Element exactlyOne = XmlUtils.getFirstChildElementNS(policy, usedPolicyNamespace, "ExactlyOne");
        if (exactlyOne != null) {
            Element all = XmlUtils.getFirstChildElementNS(exactlyOne, usedPolicyNamespace, "All");
            if (all != null) {
                newPolicy = getAddressingPolicy(all, usedPolicyNamespace);
            }
        } else {
            newPolicy = getAddressingPolicy(policy, usedPolicyNamespace);
        }
        return newPolicy;
    }

    private static Policy getAddressingPolicy(Element wsamAddressingElm, String usedPolicyNamespace) {
        // check if found reference is addressing policy
        Element wsAddressing = XmlUtils.getFirstChildElementNS(wsamAddressingElm, WsaUtils.WS_A_NAMESPACE_200705,
                "Addressing");
        Element addressingPolicy = null;
        Policy newPolicy = PolicyDocument.Factory.newInstance().addNewPolicy();
        Addressing newAddressing = null;
        if (wsAddressing != null) {
            newAddressing = newPolicy.addNewAddressing();
            String optional = wsAddressing.getAttributeNS(usedPolicyNamespace, "Optional");
            if (!StringUtils.isNullOrEmpty(optional) && optional.equals(OptionalType.TRUE.toString())) {
                newAddressing.setOptional(OptionalType.TRUE);
            } else {
                newAddressing.setOptional(OptionalType.FALSE);
            }
            addressingPolicy = XmlUtils.getFirstChildElementNS(wsAddressing, usedPolicyNamespace, "Policy");
            if (addressingPolicy != null) {
                Element exactlyOne = XmlUtils.getFirstChildElementNS(addressingPolicy, usedPolicyNamespace, "ExactlyOne");
                if (exactlyOne != null) {
                    Element all = XmlUtils.getFirstChildElementNS(exactlyOne, usedPolicyNamespace, "All");
                    if (all != null) {
                        getAddressingAnonymous(all, newAddressing);
                    }
                } else {
                    getAddressingAnonymous(addressingPolicy, newAddressing);
                }

            }
        }
        Element usingAddressing = XmlUtils.getFirstChildElementNS(wsamAddressingElm, WsaUtils.WS_A_NAMESPACE_200605,
                "UsingAddressing");
        if (usingAddressing != null) {
            // add UsingAddressing to policy
            newPolicy.addNewUsingAddressing();
        }
        return newPolicy;
    }

    private static void getAddressingAnonymous(Element addressingPolicy, Addressing newAddressing) {
        Policy innerPolicy = newAddressing.addNewPolicy();
        // check if policy has Anonymous
        Element anonymousElm = XmlUtils.getFirstChildElementNS(addressingPolicy, new QName(
                WsaUtils.WS_A_NAMESPACE_200705, "AnonymousResponses"));
        if (anonymousElm != null) {
            innerPolicy.addNewAnonymousResponses();
        } else {
            Element nonAnonymousElement = XmlUtils.getFirstChildElementNS(addressingPolicy, new QName(
                    WsaUtils.WS_A_NAMESPACE_200705, "NonAnonymousResponses"));
            if (nonAnonymousElement != null) {
                innerPolicy.addNewNonAnonymousResponses();
            }
        }
    }
}
